/*
 * ltree 和 lquery 的操作函数
 * Teodor Sigaev <teodor@stack.net>
 * contrib/ltree/lquery_op.c
 */
#include "postgres.h"

#include <ctype.h>

#include "catalog/pg_collation.h"
#include "ltree.h"
#include "miscadmin.h"
#include "utils/formatting.h"

PG_FUNCTION_INFO_V1(ltq_regex);
PG_FUNCTION_INFO_V1(ltq_rregex);

PG_FUNCTION_INFO_V1(lt_q_regex);
PG_FUNCTION_INFO_V1(lt_q_rregex);

#define NEXTVAL(x) ( (lquery*)( (char*)(x) + INTALIGN( VARSIZE(x) ) ) )

static char * fc_getlexeme(char *fc_start, char *fc_end, int *fc_len)
{
	char	   *fc_ptr;
	int			fc_charlen;

	while (fc_start < fc_end && (fc_charlen = pg_mblen(fc_start)) == 1 && t_iseq(fc_start, '_'))
		fc_start += fc_charlen;

	fc_ptr = fc_start;
	if (fc_ptr >= fc_end)
		return NULL;

	while (fc_ptr < fc_end && !((fc_charlen = pg_mblen(fc_ptr)) == 1 && t_iseq(fc_ptr, '_')))
		fc_ptr += fc_charlen;

	*fc_len = fc_ptr - fc_start;
	return fc_start;
}

bool compare_subnode(ltree_level *fc_t, char *fc_qn, int fc_len, int (*fc_cmpptr) (const char *, const char *, size_t), bool fc_anyend)
{
	char	   *fc_endt = fc_t->name + fc_t->len;
	char	   *fc_endq = fc_qn + fc_len;
	char	   *fc_tn;
	int			fc_lent,
				fc_lenq;
	bool		fc_isok;

	while ((fc_qn = fc_getlexeme(fc_qn, fc_endq, &fc_lenq)) != NULL)
	{
		fc_tn = fc_t->name;
		fc_isok = false;
		while ((fc_tn = fc_getlexeme(fc_tn, fc_endt, &fc_lent)) != NULL)
		{
			if ((fc_lent == fc_lenq || (fc_lent > fc_lenq && fc_anyend)) &&
				(*fc_cmpptr) (fc_qn, fc_tn, fc_lenq) == 0)
			{

				fc_isok = true;
				break;
			}
			fc_tn += fc_lent;
		}

		if (!fc_isok)
			return false;
		fc_qn += fc_lenq;
	}

	return true;
}

int ltree_strncasecmp(const char *fc_a, const char *fc_b, size_t fc_s)
{
	char	   *fc_al = str_tolower(fc_a, fc_s, DEFAULT_COLLATION_OID);
	char	   *fc_bl = str_tolower(fc_b, fc_s, DEFAULT_COLLATION_OID);
	int			fc_res;

	fc_res = strncmp(fc_al, fc_bl, fc_s);

	pfree(fc_al);
	pfree(fc_bl);

	return fc_res;
}

/*
 * 检查 lquery_level 是否与 ltree_level 匹配
 *
 * 这考虑了所有标志，包括 LQL_NOT，但不考虑重复次数。
 */
static bool fc_checkLevel(lquery_level *fc_curq, ltree_level *fc_curt)
{
	lquery_variant *fc_curvar = LQL_FIRST(fc_curq);
	bool		fc_success;

	fc_success = (fc_curq->flag & LQL_NOT) ? false : true;

	/* numvar == 0 意味着 '*'，可以匹配任何内容 */
	if (fc_curq->numvar == 0)
		return fc_success;

	for (int fc_i = 0; fc_i < fc_curq->numvar; fc_i++)
	{
		int			(*fc_cmpptr) (const char *, const char *, size_t);

		fc_cmpptr = (fc_curvar->flag & LVAR_INCASE) ? ltree_strncasecmp : strncmp;

		if (fc_curvar->flag & LVAR_SUBLEXEME)
		{
			if (compare_subnode(fc_curt, fc_curvar->name, fc_curvar->len, fc_cmpptr,
								(fc_curvar->flag & LVAR_ANYEND)))
				return fc_success;
		}
		else if ((fc_curvar->len == fc_curt->len ||
				  (fc_curt->len > fc_curvar->len && (fc_curvar->flag & LVAR_ANYEND))) &&
				 (*fc_cmpptr) (fc_curvar->name, fc_curt->name, fc_curvar->len) == 0)
			return fc_success;

		fc_curvar = LVAR_NEXT(fc_curvar);
	}
	return !fc_success;
}

/*
 * 尝试将一个 lquery（有 qlen 项）与一个 ltree（有 tlen 项）匹配
 */
static bool fc_checkCond(lquery_level *fc_curq, int fc_qlen,
		  ltree_level *fc_curt, int fc_tlen)
{
	/* 由于此函数是递归的，它可能会导致栈溢出 */
	check_stack_depth();

	/* 病态模式可能也需要一些时间 */
	CHECK_FOR_INTERRUPTS();

	/* 当我们有查询项需要考虑时，循环进行 */
	while (fc_qlen > 0)
	{
		int			fc_low,
					fc_high;
		lquery_level *fc_nextq;

		/*
		 * 获取此查询项的最小和最大重复次数，处理
		 * 向后兼容的黑客，即低/high 字段对非 '*' 项无
		 * 意义，除非设置了 LQL_COUNT。
		 */
		if ((fc_curq->flag & LQL_COUNT) || fc_curq->numvar == 0)
			fc_low = fc_curq->low, fc_high = fc_curq->high;
		else
			fc_low = fc_high = 1;

		/*
		 * 我们可能会将 "high" 限制为剩余文本长度；这可以避免
		 * 下面的单独测试。
		 */
		if (fc_high > fc_tlen)
			fc_high = fc_tlen;

		/* 如果匹配所需项的数量不可能，则失败 */
		if (fc_high < fc_low)
			return false;

		/*
		 * 递归检查模式的其余部分是否与此项的一些匹配项
		 * 后面的每个可能的起始点匹配。
		 */
		fc_nextq = LQL_NEXT(fc_curq);
		fc_qlen--;

		for (int fc_matchcnt = 0; fc_matchcnt < fc_high; fc_matchcnt++)
		{
			/*
			 * 如果我们已经消耗了此项的可接受数量的匹配项，
			 * 并且模式的其余部分从这里开始匹配，我们就成功了。
			 */
			if (fc_matchcnt >= fc_low && fc_checkCond(fc_nextq, fc_qlen, fc_curt, fc_tlen))
				return true;

			/*
			 * 否则，尝试将另一个文本项与此查询项匹配。
			 */
			if (!fc_checkLevel(fc_curq, fc_curt))
				return false;

			fc_curt = LEVEL_NEXT(fc_curt);
			fc_tlen--;
		}

		/*
		 * 一旦我们消耗了 "high" 的匹配项，我们只能成功
		 * 如果模式的其余部分从这里开始匹配。循环进行（如果你愿意，
		 * 可以把这个想成尾递归）。
		 */
		fc_curq = fc_nextq;
	}

	/*
	 * 一旦我们没有查询项，我们只有在没有剩余
	 * 文本时才会匹配。
	 */
	return (fc_tlen == 0);
}

Datum ltq_regex(PG_FUNCTION_ARGS)
{
	ltree	   *fc_tree = PG_GETARG_LTREE_P(0);
	lquery	   *fc_query = PG_GETARG_LQUERY_P(1);
	bool		fc_res;

	fc_res = fc_checkCond(LQUERY_FIRST(fc_query), fc_query->numlevel,
					LTREE_FIRST(fc_tree), fc_tree->numlevel);

	PG_FREE_IF_COPY(fc_tree, 0);
	PG_FREE_IF_COPY(fc_query, 1);
	PG_RETURN_BOOL(fc_res);
}

Datum ltq_rregex(PG_FUNCTION_ARGS)
{
	PG_RETURN_DATUM(DirectFunctionCall2(ltq_regex,
										PG_GETARG_DATUM(1),
										PG_GETARG_DATUM(0)
										));
}

Datum lt_q_regex(PG_FUNCTION_ARGS)
{
	ltree	   *fc_tree = PG_GETARG_LTREE_P(0);
	ArrayType  *fc__query = PG_GETARG_ARRAYTYPE_P(1);
	lquery	   *fc_query = (lquery *) ARR_DATA_PTR(fc__query);
	bool		fc_res = false;
	int			fc_num = ArrayGetNItems(ARR_NDIM(fc__query), ARR_DIMS(fc__query));

	if (ARR_NDIM(fc__query) > 1)
		ereport(ERROR,
				(errcode(ERRCODE_ARRAY_SUBSCRIPT_ERROR),
				 errmsg("array must be one-dimensional")));
	if (array_contains_nulls(fc__query))
		ereport(ERROR,
				(errcode(ERRCODE_NULL_VALUE_NOT_ALLOWED),
				 errmsg("array must not contain nulls")));

	while (fc_num > 0)
	{
		if (DatumGetBool(DirectFunctionCall2(ltq_regex,
											 PointerGetDatum(fc_tree), PointerGetDatum(fc_query))))
		{

			fc_res = true;
			break;
		}
		fc_num--;
		fc_query = NEXTVAL(fc_query);
	}

	PG_FREE_IF_COPY(fc_tree, 0);
	PG_FREE_IF_COPY(fc__query, 1);
	PG_RETURN_BOOL(fc_res);
}

Datum lt_q_rregex(PG_FUNCTION_ARGS)
{
	PG_RETURN_DATUM(DirectFunctionCall2(lt_q_regex,
										PG_GETARG_DATUM(1),
										PG_GETARG_DATUM(0)
										));
}
